export interface TaskCallBackAction {
  resolve: () => void
  reject: () => void
  index: number
  message: Message
  item: any
}
export type CompleteCallBack = (op: TaskCallBackAction) => void

export interface TaskItem {
  name: string
  action: CompleteCallBack
  data: any[]
  num?: number
}

export interface AllDoneMessage {
  type: string
  text: string
}

class Task {
  private readonly original: TaskItem[] = []
  private data: TaskItem[] = []
  private _currentTask = 0
  private _currentStep = 0
  private failedTask = 0
  private failedStep = 0
  private _pause = false
  private _currentState: number[] = []
  private _messages: { type: string; message: string }[] = []
  private isRetry = false
  private readonly allDoneMessage: AllDoneMessage
  private delayTime = 1000

  private _subTask: Task | undefined
  private subTaskAllDoneMessage = '所有子任务已执行完成'

  private everySingleTaskDoneCallBack: (() => void) | undefined
  parent?: Task

  constructor(
    list: TaskItem[],
    message?: string | AllDoneMessage,
    delayTime?: number
  ) {
    const _list = []
    for (let i = 0; i < list.length; i++) {
      this._currentState.push(0)
      const data = JSON.parse(JSON.stringify(list[i].data))
      for (const f in Object.keys(data)) {
        data[f].progressTaskTask = i
        data[f].progressTaskStep = f
      }
      _list.push({ ...list[i], data })
    }
    this.original = [..._list]
    this.data = [..._list]

    if (typeof message === 'undefined') {
      this.allDoneMessage = { text: '所有任务已执行完成', type: 'info' }
    } else if (typeof message === 'string') {
      this.allDoneMessage = { text: message, type: 'info' }
    } else {
      this.allDoneMessage = message
    }

    if (delayTime > 0) this.delayTime = delayTime
  }

  /**
   * 调用执行任务
   * @param retry 是否是重试
   * @param cb 每次完成后的回调，用于刷新页面
   */
  async invoke(retry = false, cb?: () => void): Promise<void> {
    this.everySingleTaskDoneCallBack = cb
    this.isRetry = retry
    const items = retry
      ? (() => {
          const items = []
          for (let i = 0; i < this.original.length; i++) {
            items.push({
              name: this.original[i].name,
              action: this.original[i].action,
              data: this.failedList[i],
            })
          }
          return items
        })()
      : this.original

    for (let i = 0; i < items.length; i++) {
      this._currentTask = i
      if (Object.prototype.hasOwnProperty.call(items[i], 'action')) {
        for (let index = 0; index < items[i].data.length; index++) {
          if (this._pause) {
            return
          }
          const a = (): Promise<Message> =>
            new Promise((resolve, reject) => {
              this._currentStep = index
              const message = new Message()
              message.after = function () {
                resolve(this)
              }
              const item = items[this.currentTask].data[this.currentStep]
              if (this.isRetry) {
                this.failedTask = item.progressTaskTask
                this.failedStep = item.progressTaskStep
              }

              items[i].action({
                resolve: resolve as () => {},
                reject,
                index,
                message,
                item,
              })
            })

          const res = await a()
          await this.next(res, items)
          cb && cb()
          await new Promise((resolve) => setTimeout(resolve, this.delayTime))
        }
      }
    }
    if (this.allDoneMessage) {
      this.writeMsg(
        this.allDoneMessage.text ? this.allDoneMessage.text : '',
        this.allDoneMessage.type ? this.allDoneMessage.type : 'info'
      )
    }
  }

  async executeSubTask(msg: Message) {
    this._subTask = new Task(
      msg.list,
      msg.allDoneMessage || this.subTaskAllDoneMessage
    )
    this._subTask.parent = this
    await this._subTask.invoke(false, this.everySingleTaskDoneCallBack)

    if (msg.subTaskCompleteCallBack) {
      await (() =>
        new Promise((resolve, reject) => {
          const message = new Message()
          message.after = function () {
            resolve(null)
          }

          msg.subTaskCompleteCallBack &&
            msg.subTaskCompleteCallBack({
              resolve: resolve as () => {},
              reject,
              item: this.data[this.currentTask].data[this.currentStep],
              index: this.currentStep,
              message,
            })
        }))()
    }
    // this.#subTask = null
  }

  doAction(task: number, step: number): Promise<Message> {
    return new Promise((resolve, reject) => {
      this._currentStep = step
      const m = new Message()
      m.after = function () {
        resolve(this)
      }
      this.original[task].action({
        resolve: resolve as () => {},
        reject,
        index: step,
        message: m,
        item: this.data[task].data[step],
      })
    })
  }

  /**
   *
   * @param msg [Message]
   * @param items
   */
  async next(msg: Message, items: TaskItem[]): Promise<boolean | null> {
    this.writeMsg(msg.message, msg.type)
    switch (msg.action) {
      case 'pause':
        this._pause = true
        break
      case 'retry':
        msg = await this.retry()
        break
      case 'subTask':
        await this.executeSubTask(msg)
        break
      case 'append':
        const _list = []
        for (let i = 0; i < msg.list.length; i++) {
          this._currentState.push(0)
          const data = JSON.parse(JSON.stringify(msg.list[i].data))
          for (const f in Object.keys(data)) {
            data[f].progressTaskTask = i
            data[f].progressTaskStep = f
          }
          _list.push({ ...msg.list[i], data })
        }
        this.original.push(..._list)
        this.data.push(..._list)
        break
    }

    let flag = null
    if (msg.type === 'success') {
      if (this.isRetry) {
        if (
          Object.prototype.hasOwnProperty.call(
            this.original[this.failedTask],
            'data'
          )
        ) {
          this.original[this.failedTask].data[
            this.failedStep
          ].progressTaskState = 0
        } else {
          console.error('Task ' + this.failedTask + ' not exists.')
        }
      } else {
        if (
          Object.prototype.hasOwnProperty.call(
            this.original[this._currentTask],
            'data'
          )
        ) {
          this.original[this._currentTask].data[
            this._currentStep
          ].progressTaskState = 0
        } else {
          console.error('Task ' + this._currentTask + ' not exists.')
        }
      }

      flag = true
    } else if (msg.type === 'failed') {
      if (this.isRetry) {
        if (
          Object.prototype.hasOwnProperty.call(
            this.original[this.failedTask],
            'data'
          )
        ) {
          this.original[this.failedTask].data[
            this.failedStep
          ].progressTaskState = 1
        } else {
          console.error('Task ' + this.failedTask + ' not exists.')
        }
      } else {
        if (
          Object.prototype.hasOwnProperty.call(
            this.original[this._currentTask],
            'data'
          )
        ) {
          this.original[this._currentTask].data[
            this._currentStep
          ].progressTaskState = 1
        } else {
          console.error('Task ' + this._currentTask + ' not exists.')
        }
      }
      flag = false
    }

    if (this._currentStep < this.original[this._currentTask].data.length) {
      this._currentState[this._currentTask] = this._currentStep + 1
    }
    return flag
  }

  async retry(): Promise<Message> {
    const times = 3
    let res: Message = new Message()
    for (let i = 1; i <= times; i++) {
      this.writeMsg(
        '[' +
          this._currentTask +
          '][' +
          this._currentStep +
          ']正在进行第' +
          i +
          '次尝试',
        'info'
      )
      res = await this.doAction(this._currentTask, this._currentStep)
      if (res.action !== 'retry') {
        break
      }
    }

    if (res.action === 'retry') {
      res.type = 'failed'
      this.writeMsg('执行超过最大限度，已跳过', res.type)
    } else {
      this.writeMsg(res.message, res.type)
    }
    return res
  }

  writeMsg(message: string, type: string): void {
    if (this.parent) {
      this.parent.writeMsg(message, type)
      return
    }
    this._messages.unshift({ message, type })
  }

  /**
   * 根据下标获取执行进度
   * @param {number} index 下标
   */
  progress(index: number): number {
    const current = this._currentState[index]
    const len =
      this.failedList[index] !== undefined ? this.failedList[index].length : 0
    const total = this.isRetry ? len : this.original[index].data.length
    const per = parseInt(((current / total) * 100).toString())
    return per > 100 ? 100 : !isNaN(per) ? per : 0
  }

  pause(): void {
    this._pause = true
    this.subTask && this.subTask.pause()
  }

  get totalSuccessNum() {
    let num = 0
    for (const item of this.successList) {
      num += item.length
    }
    return num
  }

  get originalData() {
    return this.original
  }
  get currentState() {
    return this._currentState
  }
  get currentTask() {
    return this._currentTask
  }
  get currentStep() {
    return this._currentStep
  }
  get messages() {
    return this._messages
  }
  get subTask() {
    return this._subTask
  }
  get failedList() {
    return this.data.map((arr) =>
      arr.data.filter((item) => item.progressTaskState === 1)
    )
  }
  failedListCount(index: number) {
    return this.failedList[index] !== undefined
      ? this.failedList[index].length
      : 0
  }
  get successList() {
    return this.data.map((arr) =>
      arr.data.filter((item) => item.progressTaskState === 0)
    )
  }
  successListCount(index: number) {
    return this.successList[index] !== undefined
      ? this.successList[index].length
      : 0
  }
}

interface MessageExtProp {
  subTaskCompleteCallBack?: CompleteCallBack
  allDoneMessage?: AllDoneMessage
}
class Message {
  message = ''
  type = ''
  action = ''
  list: TaskItem[] = []
  subTaskCompleteCallBack?: CompleteCallBack
  allDoneMessage?: AllDoneMessage

  success = (m: string) => this.output(m, 'success')
  failed = (m: string) => this.output(m, 'failed')
  warning = (m: string) => this.output(m, 'warning')
  info = (m: string) => this.output(m, 'info')

  pause = () => this.output('已终止', 'info', 'pause')
  retry = (m: string) => this.output(m, 'info', 'retry')
  subTask = (m: string, list: TaskItem[], ext: MessageExtProp = {}) =>
    this.output(m, 'success', 'subTask', list, ext)
  append = (m: string, list: TaskItem[]) =>
    this.output(m, 'success', 'append', list)

  output(
    message: string,
    type: string,
    action = '',
    list: TaskItem[] = [],
    ext: MessageExtProp = {}
  ): Message {
    this.message = message
    this.type = type
    this.action = action
    this.list = list
    this.subTaskCompleteCallBack = ext.subTaskCompleteCallBack
    this.allDoneMessage = ext.allDoneMessage
    this.after()
    return this
  }

  after = () => {}
}

export default Task
